﻿using System;
using System.Diagnostics;
using System.Linq;
using Simp.Model.CoreTransformations;
using System.Collections.Generic;

namespace Simp.Model
{
	/// <summary>Bound calling expression:  first parameter is the bound expression, the rest are its calling parameters.</summary>
	[ExpressionName("Bound")]
	[DebuggerDisplay("{ExpressionHelper.Serialize(this)}")]
	public class BoundCallingExpression : IExpression, IParameterPolicyExpression, ISimplifiableExpression, IExpressionSerializer
	{
		private readonly IParameterBinding[] _parameterBindings;

		/// <summary>Constructor taking the total number of parameters passed to it (including the bound expression).</summary>
		/// <param name="parameterCount"></param>
		public BoundCallingExpression(int parameterCount)
		{
			if (parameterCount < 1)
			{
				throw new ArgumentException("Can't be less than one:  bound expression", "parameterCount");
			}
			_parameterBindings = ExpressionHelper.CreateParameterBinding(parameterCount);
		}

		#region IParameterPolicyExpression Members
		IParameterBinding[] IParameterPolicyExpression.ParameterBindings
		{
			get { return _parameterBindings; }
		}
		#endregion

		#region ISimplifiableExpression Members
		IExpression ISimplifiableExpression.Simplify(SimplificationMode simplificationMode, params IExpression[] parameterList)
		{
			if (parameterList == null)
			{
				throw new ArgumentNullException("parameterList");
			}
			if (parameterList.Length == _parameterBindings.Length)
			{
				IExpression boundExpression = parameterList[0];
				CustomExpression customExpression;
				CompositeExpression compositeExpression =
					ExpressionHelper.TryGetComposite(boundExpression, out customExpression);
				//	Bindings of the bound expression
				IParameterBinding[] subBindings = customExpression != null
					? customExpression.GetParameterBinding(compositeExpression.ParameterList)
					: ExpressionHelper.GetProxy(boundExpression).ParameterBindings;
				IExpression[] parametersToApply = ComputeParametersToApply(subBindings, parameterList);

				if (parametersToApply == null)
				{
					return null;
				}
				else
				{
					if (customExpression != null)
					{
						IExpression boundBody =
							SubstituteParameters(compositeExpression.ParameterList[0], subBindings, parametersToApply);

						return ExpressionHelper.Simplify(false, boundBody, simplificationMode);
					}
					else
					{
						IExpression newExpression = new CompositeExpression(boundExpression, parametersToApply);

						return ExpressionHelper.Simplify(false, newExpression, simplificationMode);
					}
				}
			}
			else
			{
				return null;
			}
		}
		#endregion

		#region IExpressionSerializer Members
		void IExpressionSerializer.Serialize(AppendOnlyStringBuilder builder, params IExpression[] parameters)
		{
			if (builder == null)
			{
				throw new ArgumentNullException("builder");
			}
			if (parameters == null)
			{
				throw new ArgumentNullException("parameters");
			}

			ExpressionHelper.SerializeGeneric(builder, this, parameters);
		}
		#endregion

		private IExpression[] ComputeParametersToApply(IParameterBinding[] subBindings, IExpression[] parameterList)
		{
			IExpression[] resultingParameters = new IExpression[subBindings.Length];
			IParameterBinding[] namedParameters = null;

			for (int i = 0; i != resultingParameters.Length; ++i)
			{
				if (namedParameters == null)
				{
					if (i >= parameterList.Length)
					{	//	Not enough parameters
						return null;
					}
					else
					{
						ParameterBindingExpression parameterBindingExpression;
						CompositeExpression compositeExpression =
							ExpressionHelper.TryGetComposite(parameterList[i + 1], out parameterBindingExpression);

						if (parameterBindingExpression == null)
						{	//	Simple unnamed parameter
							resultingParameters[i] = parameterList[i + 1];
						}
						else
						{	//	We start getting named parameter:  we collect them all
							namedParameters = new IParameterBinding[parameterList.Length - i - 1];
							namedParameters[0] = parameterBindingExpression.GetParameterBinding(compositeExpression.ParameterList);

							for (int j = 1; j != namedParameters.Length; ++j)
							{
								compositeExpression =
									ExpressionHelper.TryGetComposite(parameterList[i + 1 + j], out parameterBindingExpression);

								if (compositeExpression == null)
								{
									throw new ApplicationException("Must be named parameters from here");
								}
								namedParameters[j] =
									parameterBindingExpression.GetParameterBinding(compositeExpression.ParameterList);
							}
						}
					}
				}
				if (namedParameters != null)
				{
					var namedValue = (from binding in namedParameters
									  where binding.Name == subBindings[i].Name
									  select binding.Value).FirstOrDefault();

					if (namedValue == null)
					{
						if (subBindings[i].Value == null)
						{
							return null;
						}
						else
						{	//	Pass the default value
							resultingParameters[i] = subBindings[i].Value;
						}
					}
					else
					{
						resultingParameters[i] = namedValue;
					}
				}
			}

			return resultingParameters;
		}

		private IExpression SubstituteParameters(
			IExpression body,
			IParameterBinding[] subBindings,
			IExpression[] parametersToApply)
		{
			IDictionary<string, IExpression> knownExpressionMap = new Dictionary<string, IExpression>(subBindings.Length);

			for (int i = 0; i != subBindings.Length; ++i)
			{
				string paramName = subBindings[i].Name;
				IExpression paramValue = parametersToApply[i];

				knownExpressionMap.Add(paramName, paramValue);
			}
			IExpressionTransformation transformation = new ExpressionBindingTransformation(false, knownExpressionMap);

			return ExpressionHelper.Tranform(false, body, transformation);
		}
	}
}